/**
 * $Id: VM.js 34 2009-08-15 16:59:02Z vic.fryzel $
 */

/**
 * A simple stack machine with support for common mathematical operations,
 * common logical operations, swapping, duping, and branching.
 *
 * Usage: js VM.js path-to-js-file
 *
 * Does not support reading programs in from stdin, due to inability of 
 * SpiderMonkey shell environment to evaluate programs from strings (evalcx 
 * does not work for this.)
 *
 * Heavily inspired by provided vm.js:
 * http://www.cs.rit.edu/~ats/cc-2008-2/3/vm.js
 *
 * @author Vic Fryzel <vef3240@rit.edu>
 */

/**
 * 0-address arithmetic and test operations, operate on the stack.
 */
var add = [function () { var right = stack.pop();
		stack.push(stack.pop() + right); }];
var sub = [function () { var right = stack.pop();
		stack.push(stack.pop() - right); }];
var mul = [function () { var right = stack.pop();
		stack.push(stack.pop() * right); }];
var div = [function () { var right = stack.pop();
		stack.push(stack.pop() / right); }];
var mod = [function () { var right = stack.pop();
		stack.push(stack.pop() % right); }];
var and = [function () { var right = stack.pop();
		stack.push(stack.pop() && right); }];
var or = [function () { var right = stack.pop();
		stack.push(stack.pop() || right); }];
var xor = [function () { var right = stack.pop(); var left = stack.pop();
		stack.push(( right && !left ) || ( !right && left )); }];
var gt = [function () { var right = stack.pop();
		stack.push((stack.pop() > right) ? 1 : 0); }];
var gte = [function () { var right = stack.pop();
		stack.push((stack.pop() >= right) ? 1 : 0); }];
var lt = [function () { var right = stack.pop();
		stack.push((stack.pop() < right) ? 1 : 0); }];
var lte = [function () { var right = stack.pop();
		stack.push((stack.pop() <= right) ? 1 : 0); }];
var eq = [function () { var right = stack.pop();
		stack.push((stack.pop() == right) ? 1 : 0); }];
var neq = [function () { var right = stack.pop();
		stack.push((stack.pop() != right) ? 1 : 0); }];

/**
 * Branching operations
 */
var br = [function () { pc = code[pc++]; }];
var bt = [function () { var left = stack.pop();
		if(left) { pc = code[pc++]; } else { pc++; } }];
var bf = [function () { var left = stack.pop();
		if(!left) { pc = code[pc]; } else { pc++; } }];

/**
 * 1-address transfer operations, between stack and stack top
 * low end of stack serves as memory for variables
 */
var ld = [function () { stack.push(stack[code[pc++]]); }];
var st = [function () { stack[code[pc++]] = stack.pop(); }];

/**
 * Stack manipulation operations
 */
var push = [function () { stack.push(code[pc++]); }];
var pop = [function () { stack.pop(); }];
var swap = [function () { var right = stack.pop(); var left = stack.pop();
		stack.push(right); stack.push(left); }];
var dup = [function () { var right = stack.pop(); stack.push(right);
		stack.push(right); }];

/**
 * Prints and pops off the top item on the stack.
 */
var pr = [function () { print(stack.pop()); }];
// Limitation of javascript print function, newline forced.
var prln = [function () { print(stack.pop()); }];

/**
 * The memory stack.  This is used by most operations.  This is defined by the
 * input JS file.  Defaults to empty in case the user forgets to define a 
 * stack.
 */
var stack = [];

/**
 * The code stack.  This stores calls to specific operations.  This is defined
 * by the input JS file.  Defaults to empty in case the user forgets to define 
 * a code stack, in which case the program will do nothing.  This stack of 
 * operations is 0-based.
 */
var code = [];

/**
 * The program counter.  This is initialized to 0 by run().
 */
var pc;

/**
 * Interprets the global code stack, which is defined via the input JS file.
 */
function run() {
	// start at address 0
	pc = 0;
	// quit when beyond code[]
	var type;
	while (pc < code.length) {
		type = typeof code[pc][0];
		if(type == "function") {
			// call instruction with pc set beyond operation
			code[pc++][0]();
		} else {
			print("Error: Invalid operation \"" + code[pc][0] + "\" at pc="
					+ pc);
			break;
		}
	}
}

// Load the program specified by the first command-line argument, or show
// usage information if the argument was not specified.
if(arguments[0]) {
	load(arguments[0]);
} else {
	print("Usage: js VM.js path-to-js-file");
}

// Finally, run the code in the input JS file, with the stack defined in the
// input JS file.
run();
